<?php

declare(strict_types=1);

namespace app\service\region;

use app\model\Org;
use app\model\Region;
use app\model\RegionUser;
use InvalidArgumentException;
use think\Collection;
use think\facade\Db;
use think\Paginator;

class RegionService
{
    private ?array $customerStatsCache = null;

    public function paginate(array $filters, int $page, int $pageSize): Paginator
    {
        $query = Region::order(['level' => 'asc', 'sort' => 'asc', 'id' => 'asc']);

        if (!array_key_exists('level', $filters) && !array_key_exists('parent_id', $filters)) {
            $query->where('level', 1);
        }

        if (!empty($filters['keyword'])) {
            $keyword = trim((string) $filters['keyword']);
            $query->where(static function ($subQuery) use ($keyword) {
                $subQuery->whereLike('name', '%' . $keyword . '%')
                    ->whereOr('code', 'like', '%' . $keyword . '%');
            });
        }

        if (isset($filters['status']) && $filters['status'] !== '') {
            $query->where('status', (int) $filters['status']);
        }

        if (!empty($filters['parent_id'])) {
            $query->where('parent_id', (int) $filters['parent_id']);
        }

        if (!empty($filters['level'])) {
            $query->where('level', (int) $filters['level']);
        }

        return $query->paginate([
            'list_rows' => $pageSize,
            'page' => $page,
        ]);
    }

    public function all(array $filters = []): Collection
    {
        $query = Region::order(['level' => 'asc', 'sort' => 'asc', 'id' => 'asc']);

        if (isset($filters['status']) && $filters['status'] !== '') {
            $query->where('status', (int) $filters['status']);
        }

        if (!empty($filters['level'])) {
            $query->where('level', (int) $filters['level']);
        }

        return $query->select();
    }

    public function findById(int $id): ?Region
    {
        return Region::find($id);
    }

    public function create(array $payload): Region
    {
        $data = $this->extractPayload($payload, true);

        [$level, $pathBase] = $this->computeHierarchy($data['parent_id'] ?? 0);
        $data['level'] = $level;

        /** @var Region $region */
        $region = Db::transaction(function () use ($data, $pathBase) {
            /** @var Region $region */
            $region = Region::create($data);
            $region->path = $this->buildOwnPath($pathBase, (int) $region->id);
            $region->save();
            return $region;
        });

        $this->customerStatsCache = null;

        return $this->findById((int) $region->id);
    }

    public function update(Region $region, array $payload): Region
    {
        $data = $this->extractPayload($payload, false, $region);

        $parentChanged = false;
        if (array_key_exists('parent_id', $data) && (int) $data['parent_id'] !== (int) $region->parent_id) {
            $parentChanged = true;
            $this->assertNoCycle($region, (int) $data['parent_id']);
        }

        return Db::transaction(function () use ($region, $data, $parentChanged) {
            $oldPath = $region->path;

            if ($parentChanged) {
                [$level, $basePath] = $this->computeHierarchy((int) $data['parent_id']);
                $data['level'] = $level;
                $region->save($data);
                $region->path = $this->buildOwnPath($basePath, (int) $region->id);
                $region->save();
                $this->refreshDescendantPath($region, $oldPath);
            } else {
                $region->save($data);
            }

            $this->customerStatsCache = null;

            return $this->findById((int) $region->id);
        });
    }

    public function delete(Region $region): bool
    {
        $hasChildren = Region::where('parent_id', $region->id)->count() > 0;
        if ($hasChildren) {
            throw new InvalidArgumentException('请先删除子区域');
        }

        $orgExists = Org::where('region_id', $region->id)->count() > 0;
        if ($orgExists) {
            throw new InvalidArgumentException('区域存在关联组织，无法删除');
        }

        $userExists = RegionUser::where('region_id', $region->id)->count() > 0;
        if ($userExists) {
            throw new InvalidArgumentException('区域存在关联人员，无法删除');
        }

        $deleted = (bool) $region->delete();
        if ($deleted) {
            $this->customerStatsCache = null;
        }

        return $deleted;
    }

    public function format(Region $region): array
    {
        $stats = $this->getCustomerStats();
        $regionStats = $stats[(int) $region->id] ?? ['seller' => 0, 'buyer' => 0];

        return [
            'id' => (int) $region->id,
            'name' => $region->name,
            'code' => $region->code,
            'parent_id' => (int) $region->parent_id,
            'level' => (int) $region->level,
            'path' => $region->path,
            'sort' => (int) $region->sort,
            'status' => (int) $region->status,
            'seller_count' => (int) $regionStats['seller'],
            'buyer_count' => (int) $regionStats['buyer'],
            'create_time' => $region->create_time,
            'update_time' => $region->update_time,
        ];
    }

    public function formatCollection(iterable $regions): array
    {
        $results = [];
        foreach ($regions as $region) {
            if ($region instanceof Region) {
                $results[] = $this->format($region);
            }
        }

        return $results;
    }

    public function buildTree(array $regions): array
    {
        $grouped = [];
        foreach ($regions as $region) {
            $grouped[$region['parent_id']][] = $region;
        }

        $build = static function ($parentId) use (&$build, $grouped) {
            $items = $grouped[$parentId] ?? [];
            foreach ($items as &$item) {
                $item['children'] = $build($item['id']);
            }
            return $items;
        };

        return $build(0);
    }

    private function extractPayload(array $payload, bool $isCreate, ?Region $region = null): array
    {
        $data = [];

        if ($isCreate || array_key_exists('name', $payload)) {
            $name = trim((string) ($payload['name'] ?? ''));
            if ($name === '') {
                throw new InvalidArgumentException('区域名称不能为空');
            }
            $data['name'] = $name;
        }

        if ($isCreate || array_key_exists('code', $payload)) {
            $code = trim((string) ($payload['code'] ?? ''));
            if ($code === '') {
                throw new InvalidArgumentException('区域编码不能为空');
            }
            if ($region === null || $code !== $region->code) {
                $this->assertCodeUnique($code, $region ? (int) $region->id : null);
            }
            $data['code'] = $code;
        }

        if ($isCreate || array_key_exists('parent_id', $payload)) {
            $parentId = (int) ($payload['parent_id'] ?? 0);
            if ($parentId < 0) {
                $parentId = 0;
            }
            if ($region && $parentId === (int) $region->id) {
                throw new InvalidArgumentException('父级不能指向自身');
            }
            if ($parentId > 0 && Region::where('id', $parentId)->count() === 0) {
                throw new InvalidArgumentException('父区域不存在');
            }
            $data['parent_id'] = $parentId;
        }

        if ($isCreate || array_key_exists('sort', $payload)) {
            $data['sort'] = (int) ($payload['sort'] ?? 0);
        }

        if ($isCreate || array_key_exists('status', $payload)) {
            $status = (int) ($payload['status'] ?? 1);
            if (!in_array($status, [0, 1], true)) {
                throw new InvalidArgumentException('区域状态不正确');
            }
            $data['status'] = $status;
        }

        return $data;
    }

    private function computeHierarchy(int $parentId): array
    {
        if ($parentId <= 0) {
            return [1, '/'];
        }

        /** @var Region|null $parent */
        $parent = Region::find($parentId);
        if (!$parent) {
            throw new InvalidArgumentException('父区域不存在');
        }

        $level = (int) $parent->level + 1;
        if ($level > 2) {
            throw new InvalidArgumentException('当前仅支持两级区域结构');
        }

        return [
            $level,
            $parent->path ?: '/',
        ];
    }

    private function buildOwnPath(string $basePath, int $id): string
    {
        $basePath = rtrim($basePath, '/');
        return $basePath . '/' . $id . '/';
    }

    private function refreshDescendantPath(Region $region, string $oldPath): void
    {
        $descendants = Region::whereLike('path', $oldPath . '%')->select();
        foreach ($descendants as $child) {
            $newPath = str_replace($oldPath, $region->path, (string) $child->path);
            $child->path = $newPath;
            $segments = array_filter(explode('/', trim($newPath, '/')));
            $child->level = count($segments) ?: 1;
            $child->save();
        }
    }

    private function getCustomerStats(): array
    {
        if ($this->customerStatsCache !== null) {
            return $this->customerStatsCache;
        }

        /** @var Collection<int, Region> $regions */
        $regions = Region::field(['id', 'parent_id', 'level', 'path'])->select();
        if ($regions->isEmpty()) {
            return $this->customerStatsCache = [];
        }

        $regionMap = [];
        foreach ($regions as $region) {
            $regionMap[(int) $region->id] = $region;
        }

        $rootMap = [];
        $resolveRoot = function (int $regionId) use (&$resolveRoot, $regionMap, &$rootMap): ?int {
            if (array_key_exists($regionId, $rootMap)) {
                return $rootMap[$regionId];
            }

            $region = $regionMap[$regionId] ?? null;
            if (!$region) {
                $rootMap[$regionId] = null;
                return null;
            }

            if ((int) $region->level <= 1) {
                $rootMap[$regionId] = (int) $region->id;
                return $rootMap[$regionId];
            }

            $path = trim((string) $region->path, '/');
            if ($path !== '') {
                $parts = explode('/', $path);
                $rootId = (int) ($parts[0] ?? 0);
                if ($rootId > 0) {
                    $rootMap[$regionId] = $rootId;
                    return $rootId;
                }
            }

            $parentId = (int) $region->parent_id;
            if ($parentId > 0) {
                $root = $resolveRoot($parentId);
                $rootMap[$regionId] = $root;
                return $root;
            }

            $rootMap[$regionId] = null;
            return null;
        };

        $stats = [];
        foreach ($regionMap as $id => $region) {
            if ((int) $region->level === 1) {
                $stats[$id] = ['seller' => 0, 'buyer' => 0];
            }
        }

        $orgs = Org::field(['region_id', 'org_type', 'status'])
            ->where('status', Org::STATUS_ACTIVE)
            ->select();

        foreach ($orgs as $org) {
            $regionId = (int) ($org->region_id ?? 0);
            if ($regionId <= 0) {
                continue;
            }

            $rootId = $resolveRoot($regionId);
            if ($rootId === null) {
                continue;
            }

            if (!isset($stats[$rootId])) {
                $stats[$rootId] = ['seller' => 0, 'buyer' => 0];
            }

            $type = (string) $org->org_type;
            switch ($type) {
                case Org::TYPE_SELLER:
                    $stats[$rootId]['seller']++;
                    break;
                case Org::TYPE_BUYER:
                    $stats[$rootId]['buyer']++;
                    break;
                default:
                    $stats[$rootId]['seller']++;
                    $stats[$rootId]['buyer']++;
                    break;
            }
        }

        foreach ($regionMap as $id => $_region) {
            if (!isset($stats[$id])) {
                $stats[$id] = ['seller' => 0, 'buyer' => 0];
            }
        }

        $now = date('Y-m-d H:i:s');
        foreach ($regionMap as $id => $region) {
            if ((int) $region->level !== 1) {
                continue;
            }

            $expected = $stats[$id] ?? ['seller' => 0, 'buyer' => 0];
            $currentSeller = (int) ($region->seller_customer_count ?? 0);
            $currentBuyer = (int) ($region->buyer_customer_count ?? 0);

            if ($currentSeller !== (int) $expected['seller'] || $currentBuyer !== (int) $expected['buyer']) {
                Region::where('id', $id)->update([
                    'seller_customer_count' => (int) $expected['seller'],
                    'buyer_customer_count' => (int) $expected['buyer'],
                    'update_time' => $now,
                ]);
            }
        }

        return $this->customerStatsCache = $stats;
    }

    private function assertCodeUnique(string $code, ?int $excludeId = null): void
    {
        $query = Region::where('code', $code);
        if ($excludeId !== null) {
            $query->where('id', '<>', $excludeId);
        }

        if ($query->count() > 0) {
            throw new InvalidArgumentException('区域编码已存在');
        }
    }

    private function assertNoCycle(Region $region, int $newParentId): void
    {
        if ($newParentId === 0) {
            return;
        }

        if ($newParentId === (int) $region->id) {
            throw new InvalidArgumentException('父级不能指向自身');
        }

        $parent = Region::find($newParentId);
        if (!$parent) {
            throw new InvalidArgumentException('父区域不存在');
        }

        if (str_starts_with((string) $parent->path, (string) $region->path)) {
            throw new InvalidArgumentException('不能将区域移动到自身子级');
        }
    }
}
